#ifndef ___logging

#define ___logging 1

#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <semaphore.h>

enum log_tipos {
  LOG_NOLOG = 255,
  LOG_ERROR = 3,
  LOG_WARN = 2,
  LOG_INFO = 1,
  LOG_DEBUG = 0
};

struct log_datos {
  unsigned char minimo;
  sem_t semaforo;
};

struct log_datos * log_datos_internos = NULL;

#define log_semaforo ((log_obtener_datos_internos()) -> semaforo)

int log_registrar (int, const char *, unsigned char, const char *, ...);
#define log_minimo (*(log_minimo_nivel()))

struct log_datos * log_obtener_datos_internos(void);
unsigned char * log_minimo_nivel(void);
int log_escribir(int, const char *, int, int, unsigned char, const char *, va_list);
void log_cerrar(void);
int log_obtener_hora(char **);
int log_obtener_datos_proceso(char **, int, int);
int log_obtener_datos_tipo(char **, unsigned char);
int log_obtener_contenido(char **, const char *, va_list);

int log_registrar (int fd, const char * proceso, unsigned char tipoLog, const char * fmt, ...) {
  int pid, tid;
  va_list args;
  int resultado;
  unsigned char lm;
  if (tipoLog >= 4) return -2;
  if (!proceso) return -2;
  if (!fmt) return -2;
  lm = log_minimo;
  if (tipoLog < lm) return 0;
  pid = (int) getpid();
  tid = (int) pthread_self();
  va_start(args, fmt);
  resultado = log_escribir(fd, proceso, pid, tid, tipoLog, fmt, args);
  va_end(args);
  return resultado;
}

struct log_datos * log_obtener_datos_internos (void) {
  if (log_datos_internos) return log_datos_internos;
  log_datos_internos = malloc(sizeof(struct log_datos));
  log_datos_internos -> minimo = 0;
  sem_init(&(log_datos_internos -> semaforo), 0, 1);
  atexit(&log_cerrar);
  return log_datos_internos;
}

unsigned char * log_minimo_nivel (void) {
  unsigned char * resultado;
  sem_wait(&log_semaforo);
  resultado = &(log_datos_internos -> minimo);
  sem_post(&(log_datos_internos -> semaforo));
  return resultado;
}

int log_escribir (int fd, const char * pn, int pid, int tid, unsigned char tipoLog, const char * fmt, va_list params) {
  char ** datos = malloc(5 * sizeof(char *)); 
  int * longitud = malloc(5 * sizeof(int));
  int errores = 0;
  int resultado;
  resultado = log_obtener_hora(&datos[0]);
  if (resultado >= 0) longitud[0] = resultado; else errores ++;
  if (pn) {
    longitud[1] = strlen(pn) + 2;
    datos[1] = malloc(longitud[1] + 1);
    *(datos[1]) = '[';
    datos[1][longitud[1] - 1] = ']';
    datos[1][longitud[1]] = 0;
    memcpy(datos[1] + 1, pn, strlen(pn));
  } else {
    datos[1] = NULL;
    errores ++;
  }
  resultado = log_obtener_datos_proceso(&datos[2], pid, tid);
  if (resultado >= 0) longitud[2] = resultado; else errores ++;
  resultado = log_obtener_datos_tipo(&datos[3], tipoLog);
  if (resultado >= 0) longitud[3] = resultado; else errores ++;
  if (fmt) {
    resultado = log_obtener_contenido(&datos[4], fmt, params);
    if (resultado >= 0) longitud[4] = resultado; else errores ++;
  } else {
    datos[4] = NULL;
    errores ++;
  }
  if (errores) {
    int pos;
    for (pos = 0; pos < 5; pos ++)
      if (datos[pos]) free(datos[pos]);
    free(datos);
    free(longitud);
    return -1;
  } else {
    int longacum = 1;
    int pos;
    char * buffer;
    unsigned long int desplazamiento = 0;
    for (pos = 0; pos < 5; pos ++)
      longacum += longitud[pos];
    buffer = malloc(longacum);
    for (pos = 0; pos < 5; pos ++) {
      memcpy(buffer + desplazamiento, datos[pos], longitud[pos]);
      desplazamiento += longitud[pos];
    }
    buffer[desplazamiento] = '\n';
    return write(fd, buffer, longacum);
  }
}

int log_obtener_hora (char ** buffer) {
  struct timeval datoshora;
  struct timezone datoszona;
  int hora, minutos, segundos, milisegundos;
  gettimeofday(&datoshora, &datoszona);
  segundos = (datoshora.tv_sec - 60 * datoszona.tz_minuteswest) % 86400;
  milisegundos = datoshora.tv_usec / 1000;
  minutos = segundos / 60;
  segundos %= 60;
  hora = minutos / 60;
  minutos %= 60;
  *buffer = malloc(15);
  return sprintf(*buffer, "[%02d:%02d:%02d.%03d]", hora, minutos, segundos, milisegundos);
}

int log_obtener_datos_proceso (char ** buffer, int pid, int tid) {
  char * spid;
  char * stid;
  int acum;
  int r1, r2;
  spid = malloc(13);
  stid = malloc(13);
  r1 = sprintf(spid, "[%10u]", pid);
  r2 = sprintf(stid, "[%10u]", tid);
  if ((r1 < 0) || (r2 < 0)) {
    free(spid);
    free(stid);
    return -1;
  }
  acum = r1 + r2;
  *buffer = malloc(acum + 3);
  memcpy(*buffer, spid, r1);
  memcpy(*buffer + r1, stid, r2);
  (*buffer)[acum] = ':';
  (*buffer)[acum + 1] = ' ';
  (*buffer)[acum + 2] = 0;
  return acum + 2;
}

int log_obtener_datos_tipo (char ** buffer, unsigned char tipo) {
  switch (tipo) {
  case LOG_DEBUG:
    *buffer = malloc(8);
    if (strcpy(*buffer, "DEBUG: ")) return 7; else break;
  case LOG_INFO:
    *buffer = malloc(7);
    if (strcpy(*buffer, "INFO: ")) return 6; else break;
  case LOG_WARN:
    *buffer = malloc(7);
    if (strcpy(*buffer, "WARN: ")) return 6; else break;
  case LOG_ERROR:
    *buffer = malloc(8);
    if (strcpy(*buffer, "ERROR: ")) return 7; else break;
  default:
    *buffer = malloc(1);
    **buffer = 0;
    return 0;
  }
  free(*buffer);
  return -1;
}

int log_obtener_contenido (char ** buffer, const char * fmt, va_list params) {
  int resultado;
  unsigned int tamActual = 8;
  *buffer = malloc(tamActual);
  while (1) {
    resultado = vsnprintf(*buffer, tamActual, fmt, params);
    if ((resultado >= 0) && (resultado < tamActual)) return resultado;
    if (resultado > 0) tamActual = resultado + 1; else tamActual <<= 1;
    *buffer = realloc(*buffer, tamActual);
  }
}

void log_cerrar (void) {
  sem_destroy(&(log_datos_internos -> semaforo));
  free(log_datos_internos);
}

#endif